//     __ _____ _____ _____
//  __|  |   __|     |   | |  JSON for Modern C++
// |  |  |__   |  |  | | | |  version 3.11.3
// |_____|_____|_____|_|___|  https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2023 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT

#pragma once

#include <cmath>       // isfinite
#include <cstdint>     // uint8_t
#include <functional>  // function
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/input/input_adapters.hpp>
#include <nlohmann/detail/input/json_sax.hpp>
#include <nlohmann/detail/input/lexer.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/is_sax.hpp>
#include <nlohmann/detail/string_concat.hpp>
#include <nlohmann/detail/value_t.hpp>
#include <string>   // string
#include <utility>  // move
#include <vector>   // vector

NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail {
////////////
// parser //
////////////

enum class parse_event_t : std::uint8_t {
  /// the parser read `{` and started to process a JSON object
  object_start,
  /// the parser read `}` and finished processing a JSON object
  object_end,
  /// the parser read `[` and started to process a JSON array
  array_start,
  /// the parser read `]` and finished processing a JSON array
  array_end,
  /// the parser read a key of a value in an object
  key,
  /// the parser finished reading a JSON value
  value
};

template <typename BasicJsonType>
using parser_callback_t = std::function<bool(
    int /*depth*/, parse_event_t /*event*/, BasicJsonType & /*parsed*/)>;

/*!
@brief syntax analysis

This class implements a recursive descent parser.
*/
template <typename BasicJsonType, typename InputAdapterType>
class parser {
  using number_integer_t = typename BasicJsonType::number_integer_t;
  using number_unsigned_t = typename BasicJsonType::number_unsigned_t;
  using number_float_t = typename BasicJsonType::number_float_t;
  using string_t = typename BasicJsonType::string_t;
  using lexer_t = lexer<BasicJsonType, InputAdapterType>;
  using token_type = typename lexer_t::token_type;

 public:
  /// a parser reading from an input adapter
  explicit parser(InputAdapterType &&adapter,
                  const parser_callback_t<BasicJsonType> cb = nullptr,
                  const bool allow_exceptions_ = true,
                  const bool skip_comments = false)
      : callback(cb),
        m_lexer(std::move(adapter), skip_comments),
        allow_exceptions(allow_exceptions_) {
    // read first token
    get_token();
  }

  /*!
  @brief public parser interface

  @param[in] strict      whether to expect the last token to be EOF
  @param[in,out] result  parsed JSON value

  @throw parse_error.101 in case of an unexpected token
  @throw parse_error.102 if to_unicode fails or surrogate error
  @throw parse_error.103 if to_unicode fails
  */
  void parse(const bool strict, BasicJsonType &result) {
    if (callback) {
      json_sax_dom_callback_parser<BasicJsonType> sdp(result, callback,
                                                      allow_exceptions);
      sax_parse_internal(&sdp);

      // in strict mode, input must be completely read
      if (strict && (get_token() != token_type::end_of_input)) {
        sdp.parse_error(
            m_lexer.get_position(), m_lexer.get_token_string(),
            parse_error::create(
                101, m_lexer.get_position(),
                exception_message(token_type::end_of_input, "value"), nullptr));
      }

      // in case of an error, return discarded value
      if (sdp.is_errored()) {
        result = value_t::discarded;
        return;
      }

      // set top-level value to null if it was discarded by the callback
      // function
      if (result.is_discarded()) {
        result = nullptr;
      }
    } else {
      json_sax_dom_parser<BasicJsonType> sdp(result, allow_exceptions);
      sax_parse_internal(&sdp);

      // in strict mode, input must be completely read
      if (strict && (get_token() != token_type::end_of_input)) {
        sdp.parse_error(
            m_lexer.get_position(), m_lexer.get_token_string(),
            parse_error::create(
                101, m_lexer.get_position(),
                exception_message(token_type::end_of_input, "value"), nullptr));
      }

      // in case of an error, return discarded value
      if (sdp.is_errored()) {
        result = value_t::discarded;
        return;
      }
    }

    result.assert_invariant();
  }

  /*!
  @brief public accept interface

  @param[in] strict  whether to expect the last token to be EOF
  @return whether the input is a proper JSON text
  */
  bool accept(const bool strict = true) {
    json_sax_acceptor<BasicJsonType> sax_acceptor;
    return sax_parse(&sax_acceptor, strict);
  }

  template <typename SAX>
  JSON_HEDLEY_NON_NULL(2)
  bool sax_parse(SAX *sax, const bool strict = true) {
    (void)detail::is_sax_static_asserts<SAX, BasicJsonType>{};
    const bool result = sax_parse_internal(sax);

    // strict mode: next byte must be EOF
    if (result && strict && (get_token() != token_type::end_of_input)) {
      return sax->parse_error(
          m_lexer.get_position(), m_lexer.get_token_string(),
          parse_error::create(
              101, m_lexer.get_position(),
              exception_message(token_type::end_of_input, "value"), nullptr));
    }

    return result;
  }

 private:
  template <typename SAX>
  JSON_HEDLEY_NON_NULL(2)
  bool sax_parse_internal(SAX *sax) {
    // stack to remember the hierarchy of structured values we are parsing
    // true = array; false = object
    std::vector<bool> states;
    // value to avoid a goto (see comment where set to true)
    bool skip_to_state_evaluation = false;

    while (true) {
      if (!skip_to_state_evaluation) {
        // invariant: get_token() was called before each iteration
        switch (last_token) {
        case token_type::begin_object: {
          if (JSON_HEDLEY_UNLIKELY(
                  !sax->start_object(static_cast<std::size_t>(-1)))) {
            return false;
          }

          // closing } -> we are done
          if (get_token() == token_type::end_object) {
            if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) {
              return false;
            }
            break;
          }

          // parse key
          if (JSON_HEDLEY_UNLIKELY(last_token != token_type::value_string)) {
            return sax->parse_error(
                m_lexer.get_position(), m_lexer.get_token_string(),
                parse_error::create(
                    101, m_lexer.get_position(),
                    exception_message(token_type::value_string, "object key"),
                    nullptr));
          }
          if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) {
            return false;
          }

          // parse separator (:)
          if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) {
            return sax->parse_error(
                m_lexer.get_position(), m_lexer.get_token_string(),
                parse_error::create(
                    101, m_lexer.get_position(),
                    exception_message(token_type::name_separator,
                                      "object separator"),
                    nullptr));
          }

          // remember we are now inside an object
          states.push_back(false);

          // parse values
          get_token();
          continue;
        }

        case token_type::begin_array: {
          if (JSON_HEDLEY_UNLIKELY(
                  !sax->start_array(static_cast<std::size_t>(-1)))) {
            return false;
          }

          // closing ] -> we are done
          if (get_token() == token_type::end_array) {
            if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) {
              return false;
            }
            break;
          }

          // remember we are now inside an array
          states.push_back(true);

          // parse values (no need to call get_token)
          continue;
        }

        case token_type::value_float: {
          const auto res = m_lexer.get_number_float();

          if (JSON_HEDLEY_UNLIKELY(!std::isfinite(res))) {
            return sax->parse_error(
                m_lexer.get_position(), m_lexer.get_token_string(),
                out_of_range::create(406,
                                     concat("number overflow parsing '",
                                            m_lexer.get_token_string(), '\''),
                                     nullptr));
          }

          if (JSON_HEDLEY_UNLIKELY(
                  !sax->number_float(res, m_lexer.get_string()))) {
            return false;
          }

          break;
        }

        case token_type::literal_false: {
          if (JSON_HEDLEY_UNLIKELY(!sax->boolean(false))) {
            return false;
          }
          break;
        }

        case token_type::literal_null: {
          if (JSON_HEDLEY_UNLIKELY(!sax->null())) {
            return false;
          }
          break;
        }

        case token_type::literal_true: {
          if (JSON_HEDLEY_UNLIKELY(!sax->boolean(true))) {
            return false;
          }
          break;
        }

        case token_type::value_integer: {
          if (JSON_HEDLEY_UNLIKELY(
                  !sax->number_integer(m_lexer.get_number_integer()))) {
            return false;
          }
          break;
        }

        case token_type::value_string: {
          if (JSON_HEDLEY_UNLIKELY(!sax->string(m_lexer.get_string()))) {
            return false;
          }
          break;
        }

        case token_type::value_unsigned: {
          if (JSON_HEDLEY_UNLIKELY(
                  !sax->number_unsigned(m_lexer.get_number_unsigned()))) {
            return false;
          }
          break;
        }

        case token_type::parse_error: {
          // using "uninitialized" to avoid "expected" message
          return sax->parse_error(
              m_lexer.get_position(), m_lexer.get_token_string(),
              parse_error::create(
                  101, m_lexer.get_position(),
                  exception_message(token_type::uninitialized, "value"),
                  nullptr));
        }
        case token_type::end_of_input: {
          if (JSON_HEDLEY_UNLIKELY(m_lexer.get_position().chars_read_total ==
                                   1)) {
            return sax->parse_error(
                m_lexer.get_position(), m_lexer.get_token_string(),
                parse_error::create(
                    101, m_lexer.get_position(),
                    "attempting to parse an empty input; check that your "
                    "input string or stream contains the expected JSON",
                    nullptr));
          }

          return sax->parse_error(
              m_lexer.get_position(), m_lexer.get_token_string(),
              parse_error::create(
                  101, m_lexer.get_position(),
                  exception_message(token_type::literal_or_value, "value"),
                  nullptr));
        }
        case token_type::uninitialized:
        case token_type::end_array:
        case token_type::end_object:
        case token_type::name_separator:
        case token_type::value_separator:
        case token_type::literal_or_value:
        default:  // the last token was unexpected
        {
          return sax->parse_error(
              m_lexer.get_position(), m_lexer.get_token_string(),
              parse_error::create(
                  101, m_lexer.get_position(),
                  exception_message(token_type::literal_or_value, "value"),
                  nullptr));
        }
        }
      } else {
        skip_to_state_evaluation = false;
      }

      // we reached this line after we successfully parsed a value
      if (states.empty()) {
        // empty stack: we reached the end of the hierarchy: done
        return true;
      }

      if (states.back())  // array
      {
        // comma -> next value
        if (get_token() == token_type::value_separator) {
          // parse a new value
          get_token();
          continue;
        }

        // closing ]
        if (JSON_HEDLEY_LIKELY(last_token == token_type::end_array)) {
          if (JSON_HEDLEY_UNLIKELY(!sax->end_array())) {
            return false;
          }

          // We are done with this array. Before we can parse a
          // new value, we need to evaluate the new state first.
          // By setting skip_to_state_evaluation to false, we
          // are effectively jumping to the beginning of this if.
          JSON_ASSERT(!states.empty());
          states.pop_back();
          skip_to_state_evaluation = true;
          continue;
        }

        return sax->parse_error(
            m_lexer.get_position(), m_lexer.get_token_string(),
            parse_error::create(
                101, m_lexer.get_position(),
                exception_message(token_type::end_array, "array"), nullptr));
      }

      // states.back() is false -> object

      // comma -> next value
      if (get_token() == token_type::value_separator) {
        // parse key
        if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::value_string)) {
          return sax->parse_error(
              m_lexer.get_position(), m_lexer.get_token_string(),
              parse_error::create(
                  101, m_lexer.get_position(),
                  exception_message(token_type::value_string, "object key"),
                  nullptr));
        }

        if (JSON_HEDLEY_UNLIKELY(!sax->key(m_lexer.get_string()))) {
          return false;
        }

        // parse separator (:)
        if (JSON_HEDLEY_UNLIKELY(get_token() != token_type::name_separator)) {
          return sax->parse_error(
              m_lexer.get_position(), m_lexer.get_token_string(),
              parse_error::create(101, m_lexer.get_position(),
                                  exception_message(token_type::name_separator,
                                                    "object separator"),
                                  nullptr));
        }

        // parse values
        get_token();
        continue;
      }

      // closing }
      if (JSON_HEDLEY_LIKELY(last_token == token_type::end_object)) {
        if (JSON_HEDLEY_UNLIKELY(!sax->end_object())) {
          return false;
        }

        // We are done with this object. Before we can parse a
        // new value, we need to evaluate the new state first.
        // By setting skip_to_state_evaluation to false, we
        // are effectively jumping to the beginning of this if.
        JSON_ASSERT(!states.empty());
        states.pop_back();
        skip_to_state_evaluation = true;
        continue;
      }

      return sax->parse_error(
          m_lexer.get_position(), m_lexer.get_token_string(),
          parse_error::create(
              101, m_lexer.get_position(),
              exception_message(token_type::end_object, "object"), nullptr));
    }
  }

  /// get next token from lexer
  token_type get_token() { return last_token = m_lexer.scan(); }

  std::string exception_message(const token_type expected,
                                const std::string &context) {
    std::string error_msg = "syntax error ";

    if (!context.empty()) {
      error_msg += concat("while parsing ", context, ' ');
    }

    error_msg += "- ";

    if (last_token == token_type::parse_error) {
      error_msg += concat(m_lexer.get_error_message(), "; last read: '",
                          m_lexer.get_token_string(), '\'');
    } else {
      error_msg += concat("unexpected ", lexer_t::token_type_name(last_token));
    }

    if (expected != token_type::uninitialized) {
      error_msg += concat("; expected ", lexer_t::token_type_name(expected));
    }

    return error_msg;
  }

 private:
  /// callback function
  const parser_callback_t<BasicJsonType> callback = nullptr;
  /// the type of the last read token
  token_type last_token = token_type::uninitialized;
  /// the lexer
  lexer_t m_lexer;
  /// whether to throw exceptions in case of errors
  const bool allow_exceptions = true;
};

}  // namespace detail
NLOHMANN_JSON_NAMESPACE_END
